const http = require('http');
const fs = require('fs');
const path = require('path/posix');
const fsp = fs.promises;

// .jpg  .jpeg  --->  image/jpeg
//.png  --->  image/png
//.gig --->  image/gif
//.ico  --->  image/x-ico
//.txt --->  text/plain
//.json  --->  text/json或application/json
//.xml ---> text/xml或application/xml

// ------------------解析Body数据------------------------

/**
 * Content-Disposition:form-data;name = "file" ; filename = "xxx"
 * @param {string} headerData
 */

function parseBodyHeaders(headerData) {
    //.filter   创建一个数组  参数是测试每个元素函数，为true则通过
    let hlist = headerData.split('\r\n').filter(p => p.length > 0);

    //解析content-disposititon => hlist[0]
    //按照;切分数组，选出第二个元素开始数组
    let csplit = hlist[0].split('; ').slice(1)

    let filename
    let name
    for (let a of csplit) {
        if (a.indexOf('filename=') === 0) {
            filename = a.substring(a.indexOf('=') + 2, a.length - 1)
        } else {
            name = a.substring(a.indexOf('=') + 2, a.length - 1)
        }
    }

    let headers = {}
    let ind = 0
    let k

    //for...in 循环：只能获得对象的键名，不能获得键值
    //for...of 循环：允许遍历获得键值
    //例如遍历数组arr，
    //for item in arr:item只能得到0,1,2的索引，
    //for item of arr:能得到对应的内容
    for (let h of hlist) {
        //1、indexOf() 方法可返回某个指定的字符串值在字符串中首次出现的位置
        //第二个参数（非必须）代表可选的整数参数。规定在字符串中开始检索的位置
        //2、substring() 方法用于提取字符串中介于两个指定下标之间的字符,
        //如果省略第二个参数，如果省略该参数，那么返回的子串会一直到字符串的结尾。
        //3、trim() 去除字符串的头尾空格
        ind = h.indexOf(':')
        k = h.substring(0, ind).toLowerCase()
        headers[k] = h.substring(ind + 1).trim()
    }

    return {
        filename,
        name,
        headers,
        contentType: headers['content-type'] || ''
    }
}


function parseBody(bodyData, headers) {
    let ctype = headers['content-type']
    let bdy = ctype.substring(ctype.indexOf('=') + 1);
    let crlf_bdy = `\r\n--${bdy}`
    let header_end_index = bodyData.indexOf('\r\n\r\n', crlf_bdy.length)
    let headerData = bodyData.toString('utf8', crlf_bdy.length, header_end_index);


    //解析文件头信息
    let hd = parseBodyHeaders(headerData)

    let file = {
        start: header_end_index + 4,
        end: bodyData.indexOf(crlf_bdy, header_end_index),
        ...hd
    }

    file.length = file.end - file.start

    return file

}

//解析文件扩展名
function parseExtName(filename) {
    if (filename.length < 2) {
        return '';
    }

    let nsplit = filename.split('.').filter(p => p.length > 0);

    if (nsplit.length < 2) {
        return ''
    }

    return `.${nsplit[nsplit.length - 1]}`
}


//-------------------解析Body数据 - END-----------------------



let html = `
<!DOCTYPE html>
<html>
    <head>
        <title>First HTTP</title>
    </head>
    <body>
        你好
    </body>
</html>
`
let routerTable = {

    GET: {
        '/': async (ctx) => {
            ctx.response.setHeader('content-type', 'text/html; charset=utf-8');
            ctx.end(html);
        },
        '/test': async (ctx) => {
            let status = parseInt(Math.random() * 200) + 199;
            //statusCode代表状态码
            ctx.response.statusCode = status;
            ctx.end('test end');
        },
        '/upload': async (ctx) => {
            //createReadStream()
            // 第一个参数，读取文件的路径
            //第二个参数 highWaterMark 最高水位线,默认最多读取64K
            let stm = fs.createReadStream('./upload.html');
            stm.pipe(ctx.response, { end: false });
            stm.on('end', () => {
                ctx.end()
            })
        }

    },

    POST: {
        '/data': async (ctx) => {
            let file = parseBody(ctx.rawBody, ctx.headers);
            console.log(file);

            if (!ctx.rawBody) {
                ctx.end();
                return;
            }


            //r 可读    w 可写   x 可执行
            let fname = `${Date.now()}--${Math.random()}` + `${parseExtName(file.filename)}`;
            let fh = await fsp.open(fname, 'w+');
            let fret = await fh.write(ctx.rawBody, file.start, file.length, 0)

            //关闭打开的文件
            fh.close();


            ctx.end(`${fret.bytesWritten}`);
            //ctx.end(ctx.rawBody);


        }
    }


}

http.createServer(async (req, res) => {


    let rm = routerTable[req.method]//查询到请求方法

    let usplit = req.url.split("?");
    let pathname = usplit[0];
    let querystring = usplit[1] || '';

    if (!rm || !rm[pathname]) {
        res.statusCode = 404;
        res.end('page not found');
        return
    }

    //ctx 是context的缩写，意为请求上下文，就是打包了各种数据和方法的对象
    let ctx = {
        method: req.method,
        path: pathname,
        search: querystring,
        headers: req.headers,
        request: req,
        response: res,
        end: (data, encoding = 'utf8') => {
            res.end(data, encoding)
        },
        write: (data, encoding = 'utf8') => {
            res.write(data, encoding)
        },
        totalLength: 0,
        rawBody: null,
        bufferList: []
    }

    //请求类型：GET HEAD TRACE OPTIONS      (提交数据的请求)POST PUT PATCH    （删除资源，可选）DELETE
    //后两类类型：首字母都是P或D，只靠首字母就可判断

    await new Promise((rv, rj) => {
        if ('PD'.indexOf(req.method[0] >= 0)) {
            //发数据
            req.on('data', chunk => {
                ctx.bufferList.push(chunk);
                ctx.totalLength += chunk.length;
            })
        } else {
            //不该发数据
            req.on('data', data => {
                res.statusCode = 400;
                res.end('bad resquest', () => {
                    req.destroy();
                })
            })
        }

        let resolved = false;
        req.on('end', () => {
            resolved = true;
            rv();
        })

        req.on('close', () => {
            (!resolved) && rv();
        })

    })

    if (!res.writable || res.writableEnded) {
        return
    }

    if (ctx.bufferList.length > 0) {
        ctx.rawBody = Buffer.concat(ctx.bufferList, ctx.totalLength);
        ctx.bufferList = [];
    }

    rm[pathname](ctx);

}).listen(3456)



